﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Web.Routing;

namespace System.Web.Mvc
{

	[AttributeUsage(AttributeTargets.Property)]
	public class AtLeastRequiredAttribute : ValidationAttribute, IClientValidatable
	{
		private readonly string[] _properties;
		private int _Min = 1;
		private int _Max = int.MaxValue;

		/// <summary>
		/// The maximum number of items in the group that can be filled in.
		/// </summary>
		public int MaxSelected
		{
			get
			{
				return _Max;
			}
			set
			{
				_Max = value;
			}
		}

		/// <summary>
		/// The minimum number of items in the group that need to be filled in.
		/// </summary>
		public int MinSelected
		{
			get
			{
				return _Min;
			}
			set
			{
				_Min = value;
			}
		}
		
		/// <summary>
		/// The group identifier
		/// </summary>
		public string Group { get; set; }

		public AtLeastRequiredAttribute(string group)
		{
			Group = group;
		}

		private void SetDefaults()
		{
			if (MinSelected <= 0)
			{
				MinSelected = 1;
			}

			if (MaxSelected <= MinSelected)
			{
				MaxSelected = MinSelected;
			}

		}

		protected override ValidationResult IsValid(object value, ValidationContext validationContext)
		{

			SetDefaults();

			var properties = validationContext
				.ObjectType
				.GetProperties()
				.Where(p => p.GetCustomAttributes(true)
					.Any(a => (a is AtLeastRequiredAttribute)
						&& ((AtLeastRequiredAttribute)a).Group == this.Group))
				.Distinct();

			var selectedCount = 0;

			foreach (var property in properties.Select(p => p.Name))
			{
				var propertyInfo = validationContext.ObjectType.GetProperty(property);

				var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
				if (propertyValue is string && !string.IsNullOrEmpty(propertyValue as string))
				{
					selectedCount++;
				}
				else
					if (propertyValue != null)
					{
						selectedCount++;
					}
			}

			if (selectedCount >= MinSelected && selectedCount <= MaxSelected) return null;

			return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
		}

		public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
		{
			SetDefaults();

			var rule = new ModelClientValidationRule()
			{
				ErrorMessage = ErrorMessage,
				ValidationType = "atleastrequired"
			};

			rule.ValidationParameters["properties"] = string.Join(",", metadata.Model.GetType()
				.GetProperties()
				.Where(p => p.GetCustomAttributes(true)
					.Any(a => (a is AtLeastRequiredAttribute)
						&& ((AtLeastRequiredAttribute)a).Group == this.Group))
				.Distinct()
				.Select(p => p.Name).ToArray());

			rule.ValidationParameters["group"] = this.Group;
			rule.ValidationParameters["minselected"] = this.MinSelected;
			rule.ValidationParameters["maxselected"] = this.MaxSelected;

			yield return rule;
		}

	}
}
